package org.etnaframework.module.base.utils;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * 堆栈信息打印相关工具库
 *
 * @author jasyaf
 * @since 2023-12-24
 */
public class StackTraceUtils {

    /**
     * 异常类名的分隔符
     */
    public static final String EXCEPTION_SPLIT = "@";

    private static final Set<String> IGNORE_PREFIX = new LinkedHashSet<>();

    static {
        addIgnorePrefix("sun.");
        addIgnorePrefix("com.sun.");
        addIgnorePrefix("jdk.");
        addIgnorePrefix("java.");
        addIgnorePrefix("javax.");
        addIgnorePrefix("redis.");
        addIgnorePrefix("io.lettuce.");
        addIgnorePrefix("com.baomidou.");
        addIgnorePrefix("org.mybatis.");
        addIgnorePrefix("org.springframework.");
    }

    public static void addIgnorePrefix(Class<?> clazz) {
        IGNORE_PREFIX.add(clazz.getName());
    }

    public static void addIgnorePrefix(String prefix) {
        IGNORE_PREFIX.add(prefix);
    }

    /**
     * 省略框架层部分的堆栈，尽量保留业务相关的堆栈信息
     */
    private static boolean _needIgnore(String clazzName) {
        for (String prefix : IGNORE_PREFIX) {
            if (clazzName.startsWith(prefix)) {
                return true;
            }
        }
        // 省略CGLIB生成的类
        return clazzName.contains("BySpringCGLIB$$");
    }

    /**
     * 输出完整版的堆栈信息
     */
    public static String printThrowable(Throwable ex) {
        StringBuilder tmp = new StringBuilder();
        if (null != ex) {
            StringWriter stringWriter = new StringWriter();
            PrintWriter pw = new PrintWriter(stringWriter);
            ex.printStackTrace(pw);
            tmp.append(stringWriter).append('\n');
        }
        return tmp.toString();
    }

    /**
     * 输出异常的精简化堆栈信息，方便追溯源头
     */
    public static String printThrowableSimple(Throwable ex) {
        if (null != ex) {
            ex = ex instanceof InvocationTargetException ? ex.getCause() : ex;
            StringBuilder s = new StringBuilder();
            Set<String> posSet = new HashSet<>(); // 尽量减少输出文本中重复的内容
            while (true) {
                StackTraceElement[] trace = ex.getStackTrace();
                if (trace != null) {
                    s.append(ex.getClass().getSimpleName()).append(":").append(DbMap.escapeLineBreak(ex.getMessage())).append(EXCEPTION_SPLIT);
                    boolean meetBiz = false; // 标记是否碰到了业务代码
                    for (StackTraceElement t : trace) {
                        try {
                            String clazzName = t.getClassName();
                            // 异常多数都是在Controller/Filter层抛出的，遍历到就可以停止了，这样可以减小输出堆栈的长度
                            // 处理办法是：当碰到业务代码类时，标记meetBiz=true
                            // 继续遍历，只要碰到非业务代码类（此时meetBiz=true）就结束遍历（应对从一个Controller调用另外一个Controller的方法的情况）
                            if (clazzName.endsWith("Controller") || clazzName.endsWith("Filter")) {
                                meetBiz = true;
                            } else if (meetBiz) {
                                break;
                            }
                            // 省略配置，防止过长
                            if (_needIgnore(clazzName)) {
                                continue;
                            }
                            String pos = clazzName.substring(clazzName.lastIndexOf(".") + 1) + "#" + t.getMethodName() + ":" + t.getLineNumber();
                            if (posSet.contains(pos)) {
                                continue;
                            } else {
                                posSet.add(pos);
                                s.append(pos);
                            }
                            s.append(EXCEPTION_SPLIT);
                        } catch (Throwable ignore) {
                        }
                    }
                    if (s.length() > 1) { // 去掉最后多余的分隔符
                        s.delete(s.length() - EXCEPTION_SPLIT.length(), s.length());
                    }
                }
                ex = ex.getCause();
                if (null == ex) {
                    break;
                } else {
                    s.append(" Caused by: ");
                }
            }
            return s.toString();
        }
        return "";
    }

    /**
     * 输出精简化堆栈信息，只追溯到Controller层，用于追溯请求来源
     */
    public static String printTrace() {
        StringBuilder s = new StringBuilder();
        StackTraceElement[] trace = Thread.currentThread().getStackTrace();
        boolean meetBiz = false; // 标记是否碰到了业务代码
        for (int i = 2; i < trace.length; i++) {
            try {
                StackTraceElement t = trace[i];
                String clazzName = t.getClassName();
                // 异常多数都是在Controller/Filter层抛出的，遍历到就可以停止了，这样可以减小输出堆栈的长度
                // 处理办法是：当碰到业务代码类时，标记meetBiz=true
                // 继续遍历，只要碰到非业务代码类（此时meetBiz=true）就结束遍历（应对从一个Controller调用另外一个Controller的方法的情况）
                if (clazzName.endsWith("Controller") || clazzName.endsWith("Filter")) {
                    meetBiz = true;
                } else if (meetBiz) {
                    break;
                }
                // 省略配置，防止过长
                if (_needIgnore(clazzName)) {
                    continue;
                }
                clazzName = clazzName.substring(clazzName.lastIndexOf(".") + 1);
                s.append(clazzName);
                s.append(".");
                s.append(t.getMethodName());
                s.append(":");
                s.append(t.getLineNumber());
                s.append(EXCEPTION_SPLIT);
            } catch (Throwable ignore) {
            }
        }
        if (s.length() > 1) { // 去掉最后多余的||
            s.delete(s.length() - EXCEPTION_SPLIT.length(), s.length());
        }
        return s.toString();
    }
}
